﻿using System;
using System.Text.RegularExpressions;
using FluentParser.Expressions;
using FluentParser.Expressions.Rules.Composition;
using FluentParser.Expressions.Rules.Decoration;
using FluentParser.Expressions.Rules.ErrorHandling;
using FluentParser.Expressions.Terminals.Parsing;
using FluentParser.Utils;

namespace FluentParser
{
    /// <summary>
    /// Base untyped grammar used when reference to typed grammar is not possible.
    /// Cannot be inherited.
    /// </summary>
    public abstract class Grammar
    {
        internal Grammar()
        {
            
        }

        /// <summary>
        /// The root expression which is used for parsing a text. This
        /// property must be set during the construction of a grammar.
        /// </summary>
        public IBaseExpr Root { get; protected set; }

        public ParsingNodeResult ParseTree(string text)
        {
            return ParseTree(new GrammarContext(text));
        }

        public ParsingNodeResult ParseTree(string text, bool allowToRecover)
        {
            return ParseTree(new GrammarContext(text) { AllowToRecover = allowToRecover });
        }

        /// <summary>
        /// Parse a text and returns an abstract syntax tree composed of
        /// every successful and failed attemps at parsing during the whole
        /// process.
        /// </summary>
        /// <param name="context">Context of the parsing session.</param>
        /// <returns>The parsing's resulting root node containing the complete parsing tree</returns>
        public virtual ParsingNodeResult ParseTree(GrammarContext context)
        {
            ParsingNodeResult node = new ParsingNodeResult(Root, 0);
            try
            {
                Root.ParseNode(context, node);
                return node;
            }
            catch (ParsingException)
            {
                return node;
            }
        }

        #region Helping methods

        protected static readonly Symbol EndOfText = new EndOfTextSymbolTerminal();

        protected static FutureRule<T1> FutureRule<T1>()
        {
            return new FutureRule<T1>();
        }

        protected static Symbol Symbol(string text)
        {
            return ParseSymbol.Exact(text);
        }

        protected static Tuple<T1, T2> Tuple<T1, T2>(T1 value1, T2 value2)
        {
            return new Tuple<T1, T2>(value1, value2);
        }

        protected static Tuple<T1, T2, T3> Tuple<T1, T2, T3>(T1 value1, T2 value2, T3 value3)
        {
            return new Tuple<T1, T2, T3>(value1, value2, value3);
        }

        protected static Tuple<T1, T2, T3, T4> Tuple<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4)
        {
            return new Tuple<T1, T2, T3, T4>(value1, value2, value3, value4);
        }

        protected static OperatorsExpressionRule<TSource> ParseOperators<TSource>(Expr<TSource> source, Symbol separator)
        {
            return new OperatorsExpressionRule<TSource>(source, separator);
        }
 
        #endregion Helping methods

        #region Nested classes

        protected static class ParseSymbol
        {
            public static Symbol Exact(string text)
            {
                return new ParseExactSymbolTerminal(text);
            }

            public static Symbol Regex(string pattern)
            {
                return Regex(new Regex("\\G" + pattern, RegexOptions.Compiled));
            }

            public static Symbol Regex(Regex regex)
            {
                return new ParseRegexSymbolTerminal(regex);
            }
        }

        protected static class ParseString
        {
            public static Expr<string> Exact(string text)
            {
                return new ParseExactExpressionTerminal(text);
            }

            public static Expr<string> Regex(string pattern)
            {
                return Regex(new Regex("\\G" + pattern));
            }

            public static Expr<T1> Regex<T1>(string pattern, Func<Group, T1> predicate)
            {
                return Regex(new Regex("\\G" + pattern), predicate);
            }

            public static Expr<Tuple<T1, T2>> Regex<T1, T2>(string pattern, Func<Group, T1> predicate1, Func<Group, T2> predicate2)
            {
                return Regex(new Regex("\\G" + pattern), predicate1, predicate2);
            }

            public static Expr<string> Regex(Regex regex)
            {
                return new ParseRegexExpressionTerminal<string>(regex, match => match.Value);
            }

            public static Expr<T1> Regex<T1>(Regex regex, Func<Group, T1> predicate)
            {
                return new ParseRegexExpressionTerminal<T1>(regex, match => predicate(match.Groups[0]));
            }

            public static Expr<Tuple<T1, T2>> Regex<T1, T2>(Regex regex, Func<Group, T1> predicate1, Func<Group, T2> predicate2)
            {
                return new ParseRegexExpressionTerminal<Tuple<T1, T2>>(regex, match => Tuple(predicate1(match.Groups[0]), predicate2(match.Groups[1])));
            }
        }

        protected static class ParseNumber
        {
            static ParseNumber()
            {
                Int32 = new ParseRegexExpressionTerminal<int>("[0-9]+", x => System.Int32.Parse(x.Value));
                UInt32 = new ParseRegexExpressionTerminal<uint>("[0-9]+", x => System.UInt32.Parse(x.Value));
                Int64 = new ParseRegexExpressionTerminal<long>("[0-9]+", x => System.Int64.Parse(x.Value));
                UInt64 = new ParseRegexExpressionTerminal<ulong>("[0-9]+", x => System.UInt64.Parse(x.Value));
            }

            public static Expr<int> Int32 { get; private set; }
            public static Expr<uint> UInt32 { get; private set; }
            public static Expr<long> Int64 { get; private set; }
            public static Expr<ulong> UInt64 { get; private set; }
        }

        protected static class Errors
        {
            public static Symbol SymbolFatal(string text)
            {
                return new ErrorSymbolRule(ParsingExceptionType.Error, text, null);
            }

            public static Symbol SymbolError(string text, Symbol recoverStrategy)
            {
                return new ErrorSymbolRule(ParsingExceptionType.Error, text, recoverStrategy);
            }

            public static Symbol SymbolWarning(string text)
            {
                return new ErrorSymbolRule(ParsingExceptionType.Warning, text, null);
            }

            public static Expr<T> Fatal<T>(string text)
            {
                return new ErrorExpressionRule<T>(ParsingExceptionType.Error, text, null);
            }

            public static Expr<T> Error<T>(string text, Expr<T> recoverStrategy)
            {
                return new ErrorExpressionRule<T>(ParsingExceptionType.Error, text, recoverStrategy);
            }

            public static Expr<T> Warning<T>(string text)
            {
                return new ErrorExpressionRule<T>(ParsingExceptionType.Warning, text, null);
            }
        }

        #endregion Nested classes
    }

    /// <summary>
    /// Typed grammar allowing to pass a customized context of type TContext.
    /// With this custom context, it is possible to modify a global entity
    /// during the parsing.
    /// </summary>
    /// <typeparam name="TResult">Type of the resulting value</typeparam>
    /// <typeparam name="TContext">Type of the custom context</typeparam>
    public abstract class Grammar<TResult, TContext> : Grammar where TContext : GrammarContext
    {
        /// <summary>
        /// A single grammar has to be multithread but the context type cannot be
        /// passed to every single tokens. The property Context allows to access
        /// the currently used context which is different on each thread.
        /// </summary>
        [ThreadStatic]
        private static TContext _context;

        /// <summary>
        /// The context of the current parsing session.
        /// </summary>
        protected TContext Context { get { return _context; } }

        private Expr<TResult> _root;

        /// <summary>
        /// The root expression which is used for parsing a text. This
        /// property must be set during the construction of a grammar.
        /// </summary>
        public new Expr<TResult> Root
        {
            get { return _root; }
            protected set
            {
                base.Root = value;
                _root = value;
            }
        }

        /// <summary>
        /// Parse a text and return a value of type TResult accompagnied with
        /// a couple of useful informations like a list of errors.
        /// </summary>
        /// <param name="context">Context of the parsing session.</param>
        /// <returns>The parsing's resulting value</returns>
        public GrammarResult<TResult> Parse(TContext context)
        {
            try
            {
                _context = context;
                ParsingResult<TResult> result = Root.Parse(context, 0);
                if (result == null)
                    return null;

                return new GrammarResult<TResult>(result, context.Errors);
            }
            finally
            {
                _context = null;
            }
        }

        /// <summary>
        /// Parse a text and return only a value of type TResult. All the other
        /// informations are thrown away.
        /// </summary>
        /// <param name="context">Context of the parsing session.</param>
        /// <returns>The parsing's resulting value</returns>
        public TResult ParseValue(TContext context)
        {
            try
            {
                _context = context;
                ParsingResult<TResult> result = Root.Parse(context, 0);
                if (result == null)
                    return default(TResult);

                return result.Value;
            }
            finally
            {
                _context = null;
            }
        }

        /// <summary>
        /// Parse a text and returns an abstract syntax tree composed of
        /// every successful and failed attemps at parsing during the whole
        /// process.
        /// </summary>
        /// <param name="context">Context of the parsing session. (has to be of type TContext)</param>
        /// <returns>The parsing's resulting root node containing the complete parsing tree</returns>
        public override ParsingNodeResult ParseTree(GrammarContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            TContext typedContext = context as TContext;

            if (typedContext == null)
                throw new ArgumentException("Invalid context type, " + typeof(TContext).Name + " expected", "context");

            try
            {
                _context = (TContext)context;
                return base.ParseTree(context);
            }
            finally
            {
                _context = null;
            }
        }
    }

    /// <summary>
    /// Typed grammar with a context of type GrammarContext. When no custom context
    /// is necessary, this is the type which should be derived.
    /// </summary>
    /// <typeparam name="TResult">Type of the resulting value</typeparam>
    public abstract class Grammar<TResult> : Grammar<TResult, GrammarContext>
    {
        public GrammarResult<TResult> Parse(string text)
        {
            return Parse(new GrammarContext(text));
        }

        public GrammarResult<TResult> Parse(string text, bool allowToRecover)
        {
            return Parse(new GrammarContext(text) {AllowToRecover = allowToRecover});
        }

        public TResult ParseValue(string text)
        {
            return ParseValue(new GrammarContext(text));
        }

        public TResult ParseValue(string text, bool allowToRecover)
        {
            return ParseValue(new GrammarContext(text) {AllowToRecover = allowToRecover});
        }

    }
}
